在 GSGL 中,有一個特殊的類別是 sampler,負責接收由 Javascript 中傳來的 texture。我們可以將圖像的數據放到一個特殊的紋理當中。這邊要特別注意的是,我們的紋理被映射之後一樣是矩形,並且座標系統只會在 -1 ~ 1 之間。
有了採樣器收集的紋理數據之後,我們就可以做計算,並且將值給 gl_FragColor
。以下是 GSGL 中的 texture API:
vec4 texture2D(sampler2D, vec2)
vec4 texture2DProj(sampler2D, vec3)
以下用來綁定到採樣器的立方圖紋理
vec4 textureCube(samplerCube, vec3)
vec4 textureCubeLod(samplerCube, vec3)
我們在 handleTextureLoaded
的函數當中加入以下的程式碼,跟建立 buffer 的時候很類似,我們來看一下:
function handleTextureLoaded(gl, texture,image) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); // 紋理座標垂直翻轉
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}
想要使用紋理,我們一樣需要先綁定 texture,texture 物件通常則會由 gl.createTexture
傳送過來。
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
由於 webGL 的座標系統和 canvas 座標系統不一樣,如果直接映射過去會發現圖片是倒過來的,我們使用此函式讓圖片轉正gl.texImage2D
我們透過這個函數將圖片傳入到顯示卡的紋理空間(gl.TEXTURE_2D) 。gl.texPatameteri
則是設定一堆不同的選項,來告訴 webGL,像素的存儲方式
gl.TEXTURE_MAG_FILETER
gl.LINEAR
放大過濾採用線性過濾gl.TEXTURE_MIN_FILETER
gl.LINEAR
縮小過濾採用線性過濾gl.TEXTURE_WRAP_S
gl.CLAMP_TO_EDGE
水平使用限制邊緣的方式設定紋理包裝gl.TEXTURE_WRAP_T
gl.CLAMP_TO_EDGE
垂直使用限制邊緣的方式設定紋理包裝再來就是載入圖片了:
function initImage(gl) {
var cubeTexture = gl.createTexture();
var image = new Image();
image.crossOrigin = '';
// 找不到圖片素材,用自己的大頭貼當作範例
image.src = 'https://scontent-tpe1-1.xx.fbcdn.net/v/t31.0-8/14305435_1044232042351132_2171601430048718173_o.jpg?oh=e5f4fd1ca577678383c574f2287a6d05&oe=58EACC82';
image.onload = function() {
handleTextureLoaded(gl, cubeTexture, image);
}
}
我們設定了一個 onload
函數來確保圖片載入後才開始進行 texture 綁定的工作。
到這邊,基本上我們已經順利綁定了紋理了!現在要來修改我們的 GSGL 程式:
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec4 coords;
attribute vec2 textureCoords;
uniform mat4 u_transformMatrix;
uniform mat4 u_perspectiveMatrix;
attribute vec4 colors;
varying vec4 varyingColors;
varying vec2 v_textureCoords;
void main(void) {
v_textureCoords = textureCoords;
gl_Position = u_perspectiveMatrix * u_transformMatrix * coords;
varyingColors = colors;
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 color;
varying vec4 varyingColors;
varying vec2 v_textureCoords;
uniform sampler2D sampler;
void main(void) {
gl_FragColor = texture2D(sampler, v_textureCoords);
}
</script>
首先,先加入 attribute textureCoords 讓 Javascript 傳值進來,再透過 varying v_textureCoords 將紋理座標傳給片段著色器。最後透過 texture2D 計算出目前頂點的顏色。
function createTexture(gl, program) {
var textureCoords = [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
1.0, 0.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
];
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
var textureCoords = gl.getAttribLocation(program, 'textureCoords');
gl.vertexAttribPointer(textureCoords, 2, gl.FLOAT, false, 0,0);
gl.enableVertexAttribArray(textureCoords);
return buffer;
}
我們建立了紋理座標的頂點,並且透過 buffer 將頂點陣列傳入。跟之前傳入 buffer 的方式相同,大家可以參考看看。
最後,我們再將紋理座標傳入 GSGL 中。就可以看到成果了!不過很顯然地,我的文理座標中似乎有些地方沒有設定好,導致紋理的映射方式有點不同。這邊就交給各位讀者去調整參數了。
裡頭的程式碼有點複雜,大家可能需要時常翻翻 MDN 的文件 來確認每個 API 的用途。
舉例來說,我們可以在正方體的每一個面當中都放入不同的圖片,達到更精緻的展示效果,也可以透過混和的方式來調整圖片,也能夠在 GSGL 中撰寫更多的處理函數來修改圖片中的某個顏色。全部都是靠各位的創意來表現了!
影片也能夠載入到 canvas 當中,這代表著我們也能夠將影片的內容經過 webGL 的處理後再送到網頁當中!聽起來很令人興奮吧!
今天是 webGL 修羅道的最後一天了,希望各位學得愉快。為了讓各位更了解 3D 背後的原理,我認為基本的 GSGL 理解是必要的。大家也一定發現到,裡頭有很多不必要的邏輯、初始化、綁定 buffer 等等,都不是我們應該要去煩惱的事情。
市面上當然也不乏一些優秀的 webGL 函式庫,其中最有名的就是 Three.js 了。明天開始,就是 three.js 系列囉!
雖然實際上的遊戲開發、建模上,我們絕對不會在 js 自己手動程式碼建模,雖然理論上做得到,但人生苦短,還是不要這樣折磨自己。不過其實用簡單的幾何形狀,還是可以打造出很可愛的圖形哦!
three.js 裡頭幫我們設置了 3D 開發中常見的幾種物件:
明天我會介紹 webGL 中的一些實際應用來當做 webGL 修羅道的結尾。其實自己對 webGL 的詳細運作仍然有點不熟悉,要在 10 天內摸透全部的原理實在是太累人了。不過希望透過這些 demo 與教學,可以讓各位對 webGL 不再那麼陌生,如果遇到效能瓶頸、或是需要更精緻的特效時,我們能夠透過自己撰寫簡單的 GSGL 函式來讓動畫或特效更加逼真。